package com.loser.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.loser.bizAnnotation.BizDoubleCache;
import com.loser.bizAnnotation.BizLocalCache;
import com.loser.bizAnnotation.BizRedisCache;
import com.loser.config.CacheProperties;
import com.loser.redis.core.RedisHandler;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 活动缓存类管理器
 *
 * @author loser
 * @date 2022/12/4 10:50
 */
public class CacheManger {

    /**
     * 缓存 key 对应的返回值类型
     */
    private static final Map<String, Class<?>> KEY_CLASS = new ConcurrentHashMap<>();
    /**
     * 标注 key 对应的返回值是不是集合
     */
    private static final Map<String, Boolean> KEY_TYPE = new ConcurrentHashMap<>();
    /**
     * 缓存
     */
    private static final Cache<String, String> SECOND_3 = Caffeine.newBuilder().initialCapacity(16)
            .maximumSize(256).expireAfterWrite(Duration.ofSeconds(3)).build();
    /**
     * 缓存
     */
    private static final Cache<String, String> SECOND_10 = Caffeine.newBuilder().initialCapacity(32)
            .maximumSize(512).expireAfterWrite(Duration.ofSeconds(10)).build();
    /**
     * 缓存
     */
    private static final Cache<String, String> MIN_1 = Caffeine.newBuilder().initialCapacity(1024)
            .maximumSize(4096).expireAfterWrite(Duration.ofMinutes(1)).build();
    /**
     * 缓存
     */
    private static final Cache<String, String> MIN_5 = Caffeine.newBuilder().initialCapacity(32)
            .maximumSize(512).expireAfterWrite(Duration.ofMinutes(5)).build();
    /**
     * 缓存
     */
    private static final Cache<String, String> MIN_10 = Caffeine.newBuilder().initialCapacity(32)
            .maximumSize(512).expireAfterWrite(Duration.ofMinutes(10)).build();
    /**
     * 缓存
     */
    private static final Cache<String, String> HOUR_1 = Caffeine.newBuilder().initialCapacity(16)
            .maximumSize(256).expireAfterWrite(Duration.ofHours(1)).build();

    private final RedisHandler redisHandler;

    private final CacheProperties cacheProperties;

    public CacheManger(RedisHandler redisHandler, CacheProperties cacheProperties) {
        this.redisHandler = redisHandler;
        this.cacheProperties = cacheProperties;
    }

    /**
     * redis 缓存处理
     */
    public Object redisCacheHandler(ProceedingJoinPoint pjd, BizRedisCache bizRedisCache) throws Throwable {

        // 01 构建缓存相关key
        String classKey = buildClassKey(pjd, bizRedisCache.pre(), bizRedisCache.value());
        String cacheKey = buildCacheKey(pjd, classKey);

        // 02 获取redis缓存中的值
        String value = redisHandler.get(cacheKey);
        if (StrUtil.isNotEmpty(value)) {
            // 处理空值
            if (value.contentEquals(Constant.NULL_OBJ)) {
                return null;
            }
            Object result = buildResult4Str(classKey, value);
            if (Objects.nonNull(result)) {
                return result;
            }
        }

        // 03 从非缓存中获取 添加缓存 并标记 class 映射关系
        Object result = buildResultAndMark(pjd, classKey);
        if (Objects.nonNull(result)) {
            redisHandler.set(cacheKey, JSON.toJSONString(result), bizRedisCache.timeOut());
        }
        return result;

    }

    /**
     * local cache 处理器
     */
    public Object localCacheHandler(ProceedingJoinPoint pjd, BizLocalCache bizLocalCache) throws Throwable {

        // 01 构建缓存相关key
        Cache<String, String> localCache = getLocalCache(bizLocalCache.timeType());
        String classKey = buildClassKey(pjd, bizLocalCache.pre(), bizLocalCache.value());
        String cacheKey = buildCacheKey(pjd, classKey);

        // 02 获取本地缓存中的值
        String value = localCache.getIfPresent(cacheKey);
        if (StrUtil.isNotEmpty(value)) {
            // 处理空值
            if (Constant.NULL_OBJ.equals(value)) {
                return null;
            }
            assert value != null;
            Object result = buildResult4Str(classKey, value);
            if (Objects.nonNull(result)) {
                return result;
            }
        }

        // 03 从非缓存中获取 添加缓存 并标记 class 映射关系
        Object result = buildResultAndMark(pjd, classKey);
        if (Objects.nonNull(result)) {
            localCache.put(cacheKey, JSON.toJSONString(result));
        }
        return result;

    }

    /**
     * 双层缓存处理器
     */
    public Object doubleCacheHandler(ProceedingJoinPoint pjd, BizDoubleCache bizDoubleCache) throws Throwable {

        // 01 构建缓存相关key
        Cache<String, String> localCache = getLocalCache(bizDoubleCache.timeType());
        String classKey = buildClassKey(pjd, bizDoubleCache.pre(), bizDoubleCache.value());
        String cacheKey = buildCacheKey(pjd, classKey);

        // 02 获取本地缓存中的值
        String value = localCache.getIfPresent(cacheKey);
        if (StrUtil.isNotEmpty(value)) {
            // 处理空值
            if (Constant.NULL_OBJ.equals(value)) {
                return null;
            }
            assert value != null;
            Object result = buildResult4Str(classKey, value);
            if (Objects.nonNull(result)) {
                return result;
            }
        }

        // 03 获取redis缓存中的值 并添加到本地缓存中
        value = redisHandler.get(cacheKey);
        if (StrUtil.isNotEmpty(value)) {
            // 添加本地缓存
            localCache.put(cacheKey, value);
            // 处理空值
            if (value.contentEquals(Constant.NULL_OBJ)) {
                return null;
            }
            Object result = buildResult4Str(classKey, value);
            if (Objects.nonNull(result)) {
                return result;
            }
        }

        // 04 从非缓存中获取 添加缓存 并标记 class 映射关系
        Object result = buildResultAndMark(pjd, classKey);
        if (Objects.nonNull(result)) {
            localCache.put(cacheKey, JSON.toJSONString(result));
            redisHandler.set(cacheKey, JSON.toJSONString(result), bizDoubleCache.timeOut());
        }
        return result;

    }

    private String buildCacheKey(ProceedingJoinPoint pjd, String key) {

        Object[] args = pjd.getArgs();
        if (ArrayUtil.isEmpty(args)) {
            return key;
        }
        StringBuilder sb = new StringBuilder(key);
        for (Object arg : args) {
            sb.append(":").append(arg);
        }
        return sb.toString();

    }

    /**
     * 从string中构建出返回值
     */
    private Object buildResult4Str(String key, String value) {

        if (value.contentEquals(Constant.NULL_LIST)) {
            return Collections.emptyList();
        }
        Class<?> tClass = KEY_CLASS.get(key);
        Boolean isCollection = KEY_TYPE.get(key);
        if (Objects.nonNull(tClass) && Objects.nonNull(isCollection)) {
            if (isCollection) {
                return JSON.parseArray(value, tClass);
            } else {
                return JSON.parseObject(value, tClass);
            }
        } else {
            return null;
        }

    }

    /**
     * 查询非缓存的数据
     */
    private Object buildResultAndMark(ProceedingJoinPoint pjd, String key) throws Throwable {

        Object result = pjd.proceed();
        if (Objects.isNull(result)) {
            return null;
        }
        boolean isCollection = result instanceof List;
        if (!KEY_TYPE.containsKey(key)) {
            KEY_TYPE.put(key, isCollection);
        }
        if (!KEY_CLASS.containsKey(key)) {
            if (isCollection) {
                if (CollectionUtil.isNotEmpty((List<?>) result)) {
                    KEY_CLASS.put(key, ((List<?>) result).get(0).getClass());
                } else {
                    result = Collections.emptyList();
                }
            } else {
                KEY_CLASS.put(key, result.getClass());
            }
        }
        return result;

    }

    private Cache<String, String> getLocalCache(BizLocalCache.TimeType type) {

        if (Objects.isNull(type)) {
            throw new RuntimeException("缓存不存在");
        }
        switch (type) {
            case SECOND_3:
                return SECOND_3;
            case SECOND_10:
                return SECOND_10;
            case MIN_1:
                return MIN_1;
            case MIN_5:
                return MIN_5;
            case MIN_10:
                return MIN_10;
            case HOUR_1:
                return HOUR_1;
            default:
                throw new RuntimeException("缓存不存在");
        }

    }

    private String buildClassKey(ProceedingJoinPoint pjd, String pre, String key) {

        if (StrUtil.isEmpty(pre)) {
            pre = cacheProperties.getPre();
        }
        if (StrUtil.isNotEmpty(key)) {
            if (StrUtil.isNotEmpty(pre)) {
                return pre + key;
            }
            return key;
        }
        Signature signature = pjd.getSignature();
        String declaringTypeName = signature.getDeclaringTypeName().replaceAll(cacheProperties.getPack(), "");
        return (pre + declaringTypeName.replaceAll("\\.", ":") + ":" + signature.getName()).toUpperCase();

    }

}
